﻿#include "vcl_stl.h"

#include "h_base.h"
#include "h_xml.h"
using namespace h_base;

namespace h_xml
{

// class HXmlTree, public methods

HString HXmlTree::NullStr;

HXmlTree::HXmlTree()
   : FDeleteFlag(false), FChildren(NULL), FRootFlag(true)
{
   FStrSet = new set<HString>;
   FPropertyMap = new HPropertyMap;
   FTag = FindStr("ROOT");
   FData = "";
}

HXmlTree::HXmlTree(HXmlTree *Parent, const HString &Tag)
   : FDeleteFlag(false), FChildren(NULL), FRootFlag(false)
{
   FStrSet = Parent->FStrSet;
   FPropertyMap = Parent->FPropertyMap;
   FTag = FindStr(Tag);
   FData = "";
   Parent->AddChild(this);
}

HXmlTree::HXmlTree(HXmlTree *Env)
   : FDeleteFlag(false), FChildren(NULL), FRootFlag(false)
{
   FStrSet = Env->FStrSet;
   FPropertyMap = Env->FPropertyMap;
   FTag = &NullStr;
   FData = "";
}

HXmlTree::~HXmlTree(void)
{
   Clear();

   if ( FRootFlag )
   {
      delete FStrSet;
      delete FPropertyMap;
   }
}

void HXmlTree::GetPropertyMap(map<HString, HString> &ResultMap) const
{
   ResultMap.clear();

   HPropertyKey Key;

   Key.first = this;
   Key.second = NULL;

   for ( HPropertyMap::iterator Iter = FPropertyMap->lower_bound(Key); ; Iter++ )
   {
      if ( Iter == FPropertyMap->end() || Iter->first.first != this )
      {
         break;
      }
      ResultMap[*Iter->first.second] = Iter->second;
   }
}

bool HXmlTree::operator==(const HXmlTree &rhs)
{
   if ( *FTag != *rhs.FTag || FData != rhs.FData )
   {
      return false;
   }

   int ChildQty = FChildren == NULL ? 0 : FChildren->size();
   int ChildQty_rhs = rhs.FChildren == NULL ? 0 : rhs.FChildren->size();

   if ( ChildQty != ChildQty_rhs )
   {
      return false;
   }

   map<HString, HString> TmpMap;
   map<HString, HString> TmpMap_rhs;

   GetPropertyMap(TmpMap);
   rhs.GetPropertyMap(TmpMap_rhs);

   if ( TmpMap != TmpMap_rhs )
   {
      return false;
   }

   if ( ChildQty == 0 )
   {
      return true;
   }

   for ( int i = 0; i < FChildren->size(); i++ )
   {
      if ( *(*FChildren)[i] != *(*rhs.FChildren)[i] )
      {
         return false;
      }
   }

   return true;
}

bool HXmlTree::operator!=(const HXmlTree &rhs)
{
   return !(*this == rhs );
}

const HString& HXmlTree::GetErrorString(void)
{
   return FErrorString;
}

void HXmlTree::SetTag(const HString &Tag)
{
   FTag = FindStr(Tag);
}

const HString& HXmlTree::GetTag(void)
{
   return *FTag;
}

void HXmlTree::SetData(const HString &Data)
{
   FData = Data;
}

const HString& HXmlTree::GetData(void)
{
   return FData;
}

void HXmlTree::SetDeleteFlag()
{
   FDeleteFlag = true;
}

void HXmlTree::SetProperty(const HString &PropertyName, const HString &PropertyValue, const HString &Default)
{
   HPropertyKey Key;

   Key.first = this;
   Key.second = FindStr(PropertyName);

   if ( PropertyValue == Default )
   {
      (*FPropertyMap)[Key] = "";
   }
   else
   {
      (*FPropertyMap)[Key] = PropertyValue;
   }
}

void HXmlTree::SetProperty(const char *Tag, int Num)
{
   char TmpStr[200 + 1];

   snprintf(TmpStr, 200, "%d", Num);
   SetProperty(Tag, HString(TmpStr));
}

const HString HXmlTree::GetProperty(const HString &PropertyName, const HString &Default)
{
   HPropertyKey Key;

   Key.first = this;
   Key.second = FindStr(PropertyName);

   HPropertyMap::const_iterator Iter = FPropertyMap->find(Key);

   if ( Iter == FPropertyMap->end() )
   {
      return Default;
   }
   else
   {
      if ( Iter->second == "" )
      {
         return Default;
      }
      else
	  {
         return Iter->second;
      }
   }
}

HString HXmlTree::GetPropertyUpper(const HString &PropertyName, const HString &Default)
{
   HString Result = GetProperty(PropertyName, Default);
   ToUpper(Result);
   return Result;
}

HString HXmlTree::GetPropertyEnv(const HString &PropertyName, const HString &Default)
{
   HString Result = GetProperty(PropertyName, Default);
   return EnvEvaluate(Result);
}

HString HXmlTree::GetPropertyEU(const HString &PropertyName, const HString &Default)
{
   HString Result = EnvEvaluate(GetProperty(PropertyName, Default));
   ToUpper(Result);
   return Result;
}

const HString HXmlTree::GetValue(const char *Tag, const HString &Default)
{
   if ( FChildren == NULL )
   {
      return Default;
   }

   for ( int i = 0; i < FChildren->size(); i++ )
   {
      if ( (*FChildren)[i]->FTag == FindStr(Tag) )
      {
         if ( (*FChildren)[i]->FDeleteFlag || (*FChildren)[i]->FData == "" )
         {
            return Default;
         }
         else
         {
            return (*FChildren)[i]->FData;
         }
      }
   }

   return Default;
}

void HXmlTree::GetValues(const char *Tag, vector<HString> &Result)
{
   HXmlTree *Node;
   vector<HXmlTree*> Nodes;

   Result.clear();
   Node = GetChild(Tag);

   if ( Node == NULL )
   {
      return;
   }

   Node->GetChildren(NULL, Nodes);

   for ( int i = 0; i < Nodes.size(); i++ )
   {
      if ( !Nodes[i]->FDeleteFlag )
      {
         Result.push_back(Nodes[i]->FData);
      }
   }
}

HString HXmlTree::GetValues(const char *Tag)
{
   vector<HString> Values;

   GetValues(Tag, Values);
   return JoinStrings(Values, '\n');
}

HString HXmlTree::GetValueEnv(const char *Tag, const HString &Default)
{
   HString Result = GetValue(Tag, Default);
   return EnvEvaluate(Result);
}

void HXmlTree::GetValuesEnv(const char *Tag, vector<HString> &Result)
{
   GetValues(Tag, Result);

   for ( int i = 0; i < Result.size(); i++ )
   {
      Result[i] = EnvEvaluate(Result[i]);
   }
}

HString HXmlTree::GetValueUpper(const char *Tag, const HString &Default)
{
   HString Result = GetValue(Tag, Default);
   ToUpper(Result);
   return Result;
}

void HXmlTree::GetValuesUpper(const char *Tag, vector<HString> &Result)
{
   GetValues(Tag, Result);

   for ( int i = 0; i < Result.size(); i++ )
   {
      ToUpper(Result[i]);
   }
}

HString HXmlTree::GetValueEU(const char *Tag, const HString &Default)
{
   HString Result = EnvEvaluate(GetValue(Tag, Default));
   ToUpper(Result);
   return Result;
}

void HXmlTree::GetValuesEU(const char *Tag, vector<HString> &Result)
{
   GetValues(Tag, Result);

   for ( int i = 0; i < Result.size(); i++ )
   {
      Result[i] = EnvEvaluate(Result[i]);
      ToUpper(Result[i]);
   }
}

void HXmlTree::SetValue(const char *Tag, const HString &Data)
{
   if ( FChildren == NULL )
   {
      if ( Data == "" )
      {
         return;
      }
      FChildren = new vector<HXmlTree*>;
   }

   HXmlTree *Node;

   for ( int i = 0; i < (*FChildren).size(); i++ )
   {
      if ( *(*FChildren)[i]->FTag == Tag )
      {
         (*FChildren)[i]->FDeleteFlag = false;
         (*FChildren)[i]->SetData(Data);
         return;
      }
   }

   Node = new HXmlTree(this, Tag);
   Node->SetData(Data);
}

void HXmlTree::SetValue(const char *Tag, int Num)
{
   char TmpStr[200 + 1];

   snprintf(TmpStr, 200, "%d", Num);
   SetValue(Tag, HString(TmpStr));
}

void HXmlTree::SetValues(const char *Tag, const vector<HString> &Data,
   const char *SubItemTag)
{
   if ( FChildren == NULL )
   {
      if ( Data.empty() )
      {
         return;
      }
      FChildren = new vector<HXmlTree*>;
   }

   int i;
   HXmlTree *Node;

   for ( i = 0; i < FChildren->size(); i++ )
   {
      if ( *(*FChildren)[i]->FTag == Tag )
      {
         break;
      }
   }

   if ( i == FChildren->size() )
   {
      if ( Data.empty() )
      {
         return;
      }
      Node = new HXmlTree(this, Tag);
   }
   else
   {
      if ( Data.empty() )
      {
         delete (*FChildren)[i];
         FChildren->erase((*FChildren).begin() + i);
         return;
      }
      Node = (*FChildren)[i];
      Node->Clear();
      Node->SetTag(Tag);
   }

   for ( i = 0; i < Data.size(); i++ )
   {
      Node->AddValue(SubItemTag == NULL ? "item" : SubItemTag,
         Data[i] == "" ? HString(" ") : Data[i]);
   }
}

void HXmlTree::SetValues(const char *Tag, const HString &Data,
   const char *SubItemTag)
{
   vector<HString> Values;

   SplitString(Data, '\n', Values);
   SetValues(Tag, Values, SubItemTag);
}

void HXmlTree::AddValue(const char *Tag, const HString &Data)
{
   HXmlTree *Node;

   Node = new HXmlTree(this, Tag);
   Node->SetData(Data);
}

HXmlTree *HXmlTree::GetChild(const char *Tag)
{
   if ( FChildren == NULL )
   {
      return NULL;
   }

   for ( int i = 0; i < FChildren->size(); i++ )
   {
      if ( *(*FChildren)[i]->FTag == Tag )
      {
         return (*FChildren)[i];
      }
   }

   return NULL;
}

HXmlTree *HXmlTree::ForceGetChild(const char *Tag)
{
   if ( FChildren == NULL )
   {
      FChildren = new vector<HXmlTree*>;
   }

   for( int i = 0; i < FChildren->size(); i++ )
   {
      if ( *(*FChildren)[i]->FTag == Tag )
         return (*FChildren)[i];
   }

   HXmlTree *Node = new HXmlTree(this, Tag);
   return Node;
}

void HXmlTree::GetChildren(const char *Tag, vector<HXmlTree*> &Children)
{
   if ( FChildren == NULL )
   {
      Children.clear();
      return;
   }

   if ( Tag == NULL )
   {
      Children = (*FChildren);
      return;
   }

   Children.clear();

   for ( int i = 0; i < FChildren->size(); i++ )
   {
      if ( *(*FChildren)[i]->FTag == Tag )
      {
         Children.push_back((*FChildren)[i]);
      }
   }
}

void HXmlTree::AddChild(HXmlTree *Child)
{
   if ( FChildren == NULL )
   {
      FChildren = new vector<HXmlTree*>;
   }

   (*FChildren).push_back(Child);
}

void HXmlTree::MoveChildTo(HXmlTree *Child, HXmlTree *Des)
{
   if ( FChildren == NULL )
   {
      return;
   }

   for ( int i = 0; i < FChildren->size(); i++ )
   {
      if ( (*FChildren)[i] == Child )
      {
         Des->AddChild(Child);
         FChildren->erase(FChildren->begin() + i);
         return;
      }
   }
}

bool HXmlTree::LoadFromFile(const HString &FileName, bool UpperTag)
{
   FILE *f;

   f = _wfopen(UnicodeString(UTF8String(FileName.c_str())).c_str(), L"rt");

   if ( f == NULL )
   {
      char ErrStr[500 + 1];
      snprintf(ErrStr, 500, "Can not open %s, errno = %d", FileName.c_str(), errno);
      FErrorString = ErrStr;
      return false;
   }

   {
      unsigned char Buf[3];
      fread(Buf, 3, 1, f);
      if ( Buf[0] != 0xef || Buf[1] != 0xbb || Buf[2] != 0xbf )
      {
         fseek(f, 0, SEEK_SET);
      }
   }

   HFileStream Stream(f);

   if ( !LoadFromStream(&Stream, UpperTag) )
   {
      return false;
   }

   fclose(f);
   return true;
}

bool HXmlTree::LoadFromStream(HStream *Stream, bool UpperTag)
{
   int i, j;
   int Len;
   int LineNo = 0;
   vector<HXmlTree*> NodeStack;
   HXmlTree *CurrentNode;
   HXmlTree *Node;
   char *s;
   char Line[4096 + 1], w[4096 + 1];
   int RootQty;
   bool CdataFlag = false;
   bool BrokenTagFlag = false;
   vector<HString> CdataVec;

   Clear();
   RootQty = 0;
   CurrentNode = NULL;

   for ( ; ; )
   {
      Len = Stream->ReadLine(Line, 4096);
      if ( Len == -1 )
      {
         break;
      }
      LineNo++;
      for ( i = 0; Line[i] == ' '; i++ )
         ;
      if ( CdataFlag )
      {
         if ( memcmp(Line + i, "]]>", 3) == 0 )
         {
            CurrentNode->FData = JoinStrings(CdataVec, '\n');
            CdataFlag = false;
         }
         else
         {
            CdataVec.push_back(Line);
         }
         continue;
      }
      if ( !BrokenTagFlag && memcmp(Line + i, "<![CDATA[", 9) == 0 )
      {
         if ( CurrentNode == NULL )
         {
            snprintf(Line, 500, "Line %d : syntax error", LineNo);
            FErrorString = Line;
            goto error;
         }
         CdataVec.clear();
         CdataFlag = true;
         continue;
      }
// 去除首尾空格
      for ( i = Len - 1; i >= 0 && Line[i] == ' '; i-- )
         ;
      Line[i + 1] = '\0';
      for ( s = Line; *s == ' '; s++ )
         ;
      if ( s[0] == '\0' )
      {
         continue;
      }
      i = 0;
// 所有行必须 '<' 开头
      if ( !BrokenTagFlag )
      {
         if ( s[i] != '<' )
         {
            snprintf(Line, 500, "Line %d : '<' missing", LineNo);
            FErrorString = Line;
            goto error;
         }
         i++;
// 忽略特殊标签行
         if ( s[i] == '!' || s[i] == '?' )
         {
            continue;
         }
         i += ReadWord(s + i, w, " >", 4096);
         if ( UpperTag )
         {
            ToUpper(w);
         }
         if ( w[0] == '\0' || w[0] == '>' )
         {
            snprintf(Line, 500, "Line %d : invalid tag(NULL)", LineNo);
            FErrorString = Line;
            goto error;
         }
         if ( w[0] == '/' ) // end of tag
         {
            if ( CurrentNode == NULL ||
                 strcmp(CurrentNode->FTag->c_str(), w + 1) != 0 )
            {
               snprintf(Line, 500, "Line %d : tag(%s) not match", LineNo, w + 1);
               FErrorString = Line;
               goto error;
            }
            CurrentNode = NodeStack.back();
            NodeStack.pop_back();
            i += ReadWord(s + i, w, " >", 4096);
            if ( strcmp(w, ">") != 0 )
            {
               snprintf(Line, 500, "Line %d : syntax error", LineNo);
               FErrorString = Line;
               goto error;
            }
            continue;
         }
         if ( !IsValidTag(w) )
         {
            snprintf(Line, 500, "Line %d : invalid tag(%s)", LineNo, w);
            FErrorString = Line;
            goto error;
         }
         if ( CurrentNode == NULL ) // 根节点
         {
            if ( RootQty != 0 )
            {
               snprintf(Line, 500, "Line %d : too many root nodes", LineNo);
               FErrorString = Line;
               goto error;
            }
            RootQty++;
            SetTag(w);
            Node = this;
         }
         else
         {
            Node = new HXmlTree(CurrentNode, w);
         }
      }
      for ( ; ; )
      {
         i += ReadWord(s + i, w, " =/>", 4096);
         if ( w[0] == '=' )
         {
            snprintf(Line, 500, "Line %d : syntax error", LineNo);
            FErrorString = Line;
            goto error;
         }
         if ( strcmp(w, "/") == 0 ||
              strcmp(w, ">") == 0 || w[0] == '\0' )
         {
            break;
         }
         if ( UpperTag )
         {
            ToUpper(w);
         }
         HString PropertyName = w;
         i += ReadWord(s + i, w, " =", 4096);
         if ( strcmp(w, "=") != 0 )
         {
            snprintf(Line, 500, "Line %d : syntax error", LineNo);
            FErrorString = Line;
            goto error;
         }
         i += ReadWord(s + i, w, " \"'", 4096);
         if ( strcmp(w, "\"") != 0 && strcmp(w, "'") != 0 )
         {
            snprintf(Line, 500, "Line %d : syntax error", LineNo);
            FErrorString = Line;
            goto error;
         }
         int Start = i;
         char Delimiter = w[0];
         for ( ; s[i] != '\0' && s[i] != Delimiter; i++ )
            ;
         if ( s[i] != Delimiter )
         {
            snprintf(Line, 500, "Line %d : syntax error", LineNo);
            FErrorString = Line;
            goto error;
         }
         memcpy(w, s + Start, i - Start);
         w[i - Start] = '\0';
         TranslateSpecialChar(w);
         Node->SetProperty(PropertyName, w);
         i++;
      }
      if ( w[0] == '\0' )
      {
         BrokenTagFlag = true;
         continue;
      }
      else
      {
         BrokenTagFlag = false;
      }
      if ( strcmp(w, "/") == 0 )
      {
         i += ReadWord(s + i, w, ">", 4096);
      }
      else
      {
         i += ReadWord(s + i, w, "<", 4096);
         if ( w[0] == '\0' ) // line of tag only
         {
            NodeStack.push_back(CurrentNode);
            CurrentNode = Node;
            continue;
         }
         if ( strcmp(w, "<") != 0 )
         {
            TranslateSpecialChar(w);
            Node->FData = w;
            i += ReadWord(s + i, w, "<", 4096);
         }
         if ( strcmp(w, "<") != 0 )
         {
            snprintf(Line, 500, "Line %d : syntax error", LineNo);
            FErrorString = Line;
            goto error;
         }
         i += ReadWord(s + i, w, ">", 4096);
         if ( w[0] != '/' )
         {
            snprintf(Line, 500, "Line %d : syntax error", LineNo);
            FErrorString = Line;
            goto error;
         }
         if ( UpperTag )
         {
            ToUpper(w);
         }
         if ( strcmp(Node->FTag->c_str(), w + 1) != 0 )
         {
            snprintf(Line, 500, "Line %d : tag(%s) not match", LineNo, w + 1);
            FErrorString = Line;
            goto error;
         }
         i += ReadWord(s + i, w, ">", 4096);
      }
      if ( strcmp(w, ">") != 0 || s[i] != '\0' )
      {
         snprintf(Line, 500, "Line %d : syntax error", LineNo);
         FErrorString = Line;
         goto error;
      }
   }

   if ( CurrentNode != NULL || RootQty == 0 )
   {
      FErrorString = "Unexpected EOF";
      goto error;
   }

   return true;

error :

   Clear();
   return false;
}

bool HXmlTree::SaveToFile(const HString &FileName)
{
   FILE *f;
   HMemStream Stream;
   char ErrStr[500 + 1];

   SaveToStream(&Stream);
   f = _wfopen(UnicodeString(UTF8String(FileName.c_str())).c_str(), L"wt");

   if ( f == NULL )
   {
      snprintf(ErrStr, 500, "Can create %s, errno = %d", FileName.c_str(), errno);
      FErrorString = ErrStr;
      return false;
   }

   if ( fwrite(Stream.GetBuf(), Stream.GetLength(), 1, f) != 1 )
   {
      fclose(f);
      snprintf(ErrStr, 500, "fwrite(%d) fail, errno = %d", Stream.GetLength(), errno);
      FErrorString = ErrStr;
      return false;
   }

   if ( fclose(f) != 0 )
   {
      snprintf(ErrStr, 500, "fclose fail, errno = %d", errno);
      FErrorString = ErrStr;
      return false;
   }

   return true;
}

void HXmlTree::SaveToStream(HStream *Stream)
{
   SaveNodeToStream(Stream, 0);
}

// class HXmlTree, private methods

void HXmlTree::Clear(void)
{
   if ( FChildren != NULL )
   {
      for( int i = 0; i < FChildren->size(); i++ )
      {
         delete (*FChildren)[i];
      }
      delete FChildren;
      FChildren = NULL;
   }

   FTag = &NullStr;
   FData = "";
   FDeleteFlag = false;

   if ( FRootFlag )
   {
      FStrSet->clear();
      FPropertyMap->clear();
      FTag = FindStr("ROOT");
   }
}

int HXmlTree::AddXmlString(char *Buf, const char *s)
{
   int Pos = 0;

   for ( ; *s != '\0'; s++ )
   {
      switch( *s )
      {
      case '&' :
         memcpy(Buf + Pos, "&amp;", 5);
         Pos += 5;
         break;
      case '<' :
         memcpy(Buf + Pos, "&lt;", 4);
         Pos += 4;
         break;
      case '>' :
         memcpy(Buf + Pos, "&gt;", 4);
         Pos += 4;
         break;
      case '\'' :
         memcpy(Buf + Pos, "&apos;", 6);
         Pos += 6;
         break;
      case '"' :
         memcpy(Buf + Pos, "&quot;", 6);
         Pos += 6;
         break;
      default :
         Buf[Pos++] = *s;
         break;
      }
   }

   return Pos;
}

void HXmlTree::SaveNodeToStream(HStream *Stream, int Level)
{
   int i, j;
   vector<HString> Text;
   char Line[8192 + 1];

   if ( FDeleteFlag )
      return;

   HPropertyKey Key;

   Key.first = this;
   Key.second = NULL;

   HPropertyMap::iterator FirstIter = FPropertyMap->lower_bound(Key);
   bool HasProperty = ( FirstIter != FPropertyMap->end() && FirstIter->first.first == this );

   if ( ( FChildren == NULL || FChildren->empty() ) && FData == "" && !HasProperty )
   {
      return;
   }

   int Pos = 0;

   memset(Line + Pos, ' ', Level * 3);
   Pos += Level * 3;
   Line[Pos++] = '<';
   memcpy(Line + Pos, FTag->c_str(), FTag->length());
   Pos += FTag->length();

   if ( HasProperty )
   {
      Line[Pos++] = ' ';
      HPropertyMap::iterator Iter;
      for ( Iter = FirstIter; Iter != FPropertyMap->end(); Iter++ )
      {
         if ( Iter->first.first != this )
         {
            break;
         }
         if ( Iter->second == "" )
         {
            continue;
         }
         memcpy(Line + Pos, Iter->first.second->c_str(), Iter->first.second->length());
         Pos += Iter->first.second->length();
         Line[Pos++] = '=';
         Line[Pos++] = '"';
         Pos += AddXmlString(Line + Pos, Iter->second.c_str());
         Line[Pos++] = '"';
         if ( Pos > 140 )
         {
            Line[Pos] = '\0';
            Stream->WriteLine(Line);
            memset(Line, ' ', Level * 3 + 3);
            Pos = Level * 3 + 3;
            continue;
         }
         Line[Pos++] = ' ';
      }
   }

   if ( ( FChildren == NULL || (*FChildren).empty() ) && FData == "" )
   {
      Line[Pos++] = '/';
      Line[Pos++] = '>';
      Line[Pos] = '\0';
      Stream->WriteLine(Line);
      return;
   }

   if ( FData != "" )
   {
      if ( strchr(FData.c_str(), '\n') == NULL )
      {
         Line[Pos++] = '>';
         Pos += AddXmlString(Line + Pos, FData.c_str());
         Line[Pos++] = '<';
         Line[Pos++] = '/';
         memcpy(Line + Pos, FTag->c_str(), FTag->length());
         Pos += FTag->length();
         Line[Pos++] = '>';
         Line[Pos] = '\0';
         Stream->WriteLine(Line);
      }
      else
      {
         Line[Pos++] = '>';
         Line[Pos] = '\0';
         Stream->WriteLine(Line);
         Stream->WriteLine("<![CDATA[");
         vector<HString> CdataVec;
         SplitString(FData, '\n', CdataVec);
         for ( int i = 0; i < CdataVec.size(); i++ )
         {
            Stream->WriteLine(CdataVec[i].c_str());
         }
         Stream->WriteLine("]]>");
         snprintf(Line, 8192, "%*s</%s>", Level * 3, "", FTag->c_str());
         Stream->WriteLine(Line);
      }
      return;
   }

   Line[Pos++] = '>';
   Line[Pos] = '\0';
   Stream->WriteLine(Line);

   for ( i = 0; i < FChildren->size(); i++ )
   {
      (*FChildren)[i]->SaveNodeToStream(Stream, Level + 1);
   }

   snprintf(Line, 8192, "%*s</%s>", Level * 3, "", FTag->c_str());
   Stream->WriteLine(Line);
}

bool HXmlTree::IsValidTag(const char *Tag)
{
   const char *s;

   if ( Tag[0] != '_' && !isalpha(Tag[0]) )
   {
      return false;
   }

   for ( s = Tag; *s != '\0'; s++ )
   {
      if ( *s != '_' && !isalnum(*s) )
      {
         return false;
      }
   }

   return true;
}

void HXmlTree::TranslateSpecialChar(char *w)
{
   int i, j;

   for ( i = 0; w[i] != '\0' && w[i] != '&'; i++ )
      ;

   for ( j = i; w[i] != '\0'; )
   {
      if ( memcmp(w + i, "&amp;", 5) == 0 )
      {
         w[j++] = '&';
         i += 5;
      }
      else if ( memcmp(w + i, "&lt;", 4) == 0 )
      {
         w[j++] = '<';
         i += 4;
      }
      else if ( memcmp(w + i, "&gt;", 4) == 0 )
      {
         w[j++] = '>';
         i += 4;
      }
      else if ( memcmp(w + i, "&apos;", 6) == 0 )
      {
         w[j++] = '\'';
         i += 6;
      }
      else if ( memcmp(w + i, "&quot;", 6) == 0 )
      {
         w[j++] = '"';
         i += 6;
      }
      else
      {
         w[j++] = '&';
         i++;
      }
      for ( ; w[i] != '\0' && w[i] != '&'; i++, j++ )
      {
         w[j] = w[i];
      }
   }

   w[j] = '\0';
}

const HString *HXmlTree::FindStr(const char *Str)
{
   if ( Str == NULL || *Str == '\0' )
   {
      return &NullStr;
   }

   return FindStr(HString(Str));
}

const HString *HXmlTree::FindStr(const HString *Str)
{
   return FindStr(*Str);
}

const HString *HXmlTree::FindStr(const HString &Str)
{
   if ( Str == "" )
   {
      return &NullStr;
   }

   set<HString>::iterator Iter = FStrSet->find(Str);

   if ( Iter == FStrSet->end() )
   {
      FStrSet->insert(Str);
      Iter = FStrSet->find(Str);
   }

   return &(*Iter);
}

}; // namespace h_base
